package com.zrf.base.knowledge.collections;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 集合工具类
 *
 * @author zrf
 *         Created by zhouruifeng on 2016/4/20.
 */
public class CollectionsUtils {

    protected CollectionsUtils() {

    }

    /**
     * 对集合中的对象进行排序.
     *
     * @param collection 集合对象
     * @param <T>        实现comparable的对象
     */
    public static <T extends Comparable> void sort(Collection<T> collection) {
        sortHandle(collection, null);
    }

    /**
     * 根据comparator对collection中的对象进行排序
     * @param collection 集合
     * @param comparator 比较
     * @param <T> 泛型
     */
    public static <T> void sort(Collection<T> collection, Comparator<T> comparator) {
        if (comparator == null) {
            return;
        }
        sortHandle(collection, comparator);
    }

    /**
     * 对集合中的对象进行排序
     * 实现思路
     * treeMap或者treeset在put的时候会直接标胶compare,将对象直接进行排序放到指定的位置，通过iteraor可以直接迭代出来正确的排序，故计划使用treeMap或者treeset
     * 但是对于compare比较相等的数据又想根据之前collection中原有的顺序进行排序，若用treeset会直接把重复的数据合并成一个，故不使用treeset
     * 使用treemap，key为对象本身，value为对象list。value的list的实现用LinkedList,因为LinkedList的插入速率快
     *
     * @param collection 集合对象
     * @param comparator comparator对象
     * @param <T>        对象
     */
    private static <T> void sortHandle(Collection<T> collection, Comparator<T> comparator) {
        /**
         * 集合为空直接返回
         */
        if (collection == null || collection.isEmpty()) {
            return;
        }

        Map<T, List<T>> treeMap = new TreeMap<>(comparator);
        for (T t : collection) {
            List<T> list = treeMap.get(t);
            if (list == null) {
                list = new LinkedList<T>();
            }
            list.add(t);
            treeMap.put(t, list);
        }

        //清空数组
        collection.clear();
        Set<T> set = treeMap.keySet();
        //向数组中按顺序添加对象
        for (T t : set) {
            collection.addAll(treeMap.get(t));
        }
    }

    /**
     * 根据数组和数组中每一个对象的其中一个属性名称得到数组中每一个对象的该字段数组，允许对象的字段重复出现在数组中
     * 属性名目前只支持get方法（get+属性名首字母大写）
     *
     * @param list         数组对象
     * @param propertyName 属性名称
     * @param <I>          数组中的元对象的属性名称所对应的对象
     * @param <T>          数组中的元对象
     * @return 数组中每一个对象的某一个字段的数组
     * @throws NoSuchMethodException     没有该方法异常
     * @throws IllegalAccessException    非法访问异常
     * @throws InvocationTargetException 当被调用的方法的内部抛出了异常而没有被捕获时，将由此异常接收
     */
    public static <I, T> List<I> getPropertyList(List<T> list, String propertyName)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        List<I> propertyList = new ArrayList<I>(list.size());
        getProperties(list, propertyName, propertyList, true);
        return propertyList;
    }

    /**
     * 根据数组和数组中每一个对象的其中一个属性名称得到数组中每一个对象的该字段数组，对对象的字段进行排重
     * 属性名目前只支持get方法（get+属性名首字母大写）
     *
     * @param list         数组对象
     * @param propertyName 属性名称
     * @param <I>          数组中的元对象的属性名称所对应的对象
     * @param <T>          数组中的元对象
     * @return 数组中每一个对象的某一个字段的数组
     * @throws NoSuchMethodException     没有该方法异常
     * @throws IllegalAccessException    非法访问异常
     * @throws InvocationTargetException 当被调用的方法的内部抛出了异常而没有被捕获时，将由此异常接收
     */
    public static <I, T> List<I> getNoRepeatPropertyList(List<T> list, String propertyName)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        List<I> propertyList = new ArrayList<I>(list.size());
        getProperties(list, propertyName, propertyList, false);
        return propertyList;
    }

    /**
     * 根据数组和数组中每一个对象的其中一个属性名称得到数组中每一个对象的该字段set，对对象的字段进行排重
     * 属性名目前只支持get方法（get+属性名首字母大写）
     *
     * @param list         数组对象
     * @param propertyName 属性名称
     * @param <I>          数组中的元对象的属性名称所对应的对象
     * @param <T>          数组中的元对象
     * @return 数组中每一个对象的某一个字段的set
     * @throws NoSuchMethodException     没有该方法异常
     * @throws IllegalAccessException    非法访问异常
     * @throws InvocationTargetException 当被调用的方法的内部抛出了异常而没有被捕获时，将由此异常接收
     */
    public static <I, T> Set<I> getPropertySet(List<T> list, String propertyName)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Set<I> propertySet = new HashSet<I>(list.size());

        getProperties(list, propertyName, propertySet, false);

        return propertySet;
    }

    private static <I, T> void getProperties(List<T> list, String propertyName, Collection<I> collection, boolean isRepeat)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        String getMathodName = getGetMethodName(propertyName);
        Method method = getMethod(list, getMathodName);
        if(method != null && list != null && !list.isEmpty()){
            for (T item : list) {
                I result = (I) method.invoke(item);
                if (!isRepeat) {
                    if (!collection.contains(result)) {
                        collection.add(result);
                    }
                } else {
                    collection.add(result);
                }
            }
        }

    }

    private static Method getMethod(List list, String propertyName) throws NoSuchMethodException{
        if (list != null && !list.isEmpty()){
            Class cls = list.get(0).getClass();
            Method method = cls.getDeclaredMethod(propertyName);
            return method;
        }else{
            return null;
        }
    }

    /**
     * 得到某个属性相同的map
     *
     * @param list         列表
     * @param propertyName 属性名称
     * @param <I>          属性的类型
     * @param <T>          对象的类型
     * @return 同属性的map，key对属性，value为同属性的对象的集合
     * @throws NoSuchMethodException     没有该方法异常
     * @throws IllegalAccessException    非法访问异常
     * @throws InvocationTargetException 当被调用的方法的内部抛出了异常而没有被捕获时，将由此异常接收
     */
    public static <I, T> Map<I, List<T>> getSamePropertyMap(List<T> list, String propertyName)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Map<I, List<T>> resultMap = null;
        String getPropertyName = getGetMethodName(propertyName);
        Method method = getMethod(list, getPropertyName);
        if (method != null && list != null && !list.isEmpty()) {
            resultMap = new HashMap<>(list.size());
            for (T item : list) {
                I invoke = (I) method.invoke(item);
                List<T> itemList = resultMap.get(invoke);
                if (itemList == null) {
                    itemList = new ArrayList<>();
                }
                itemList.add(item);
                resultMap.put(invoke, itemList);
            }
        } else {
            resultMap = new HashMap<>();
        }

        return resultMap;
    }

    /**
     * 根据属性名得到，get+属性名首字母大写
     *
     * @param propertyName 属性名
     * @return get方法的名称
     */
    private static String getGetMethodName(String propertyName) {
        return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
    }
}
